package com.zou.practice.sort;

import com.alibaba.fastjson.JSON;

/**
 * 归并排序：归并排序是建立在归并操作上的一种有效的排序算法。
 * 该算法是采用分治法（Divide and Conquer）的一个非常典型的应用。
 * 将已有序的子序列合并，得到完全有序的序列；即先使每个子序列有序，再使子序列段间有序。
 * 若将两个有序表合并成一个有序表，称为2-路归并
 *
 * @author zouyaowen
 * @date 2020-09-13
 */
public class MergeSort {
    public static void main(String[] args) {
        int[] arr = {13, 3, 2, 9, 8, 7, 11};
        System.out.println(JSON.toJSONString(arr));
        // mergeSort(arr, 0, arr.length - 1);
        // mergeSortSecond(arr);
        mergeSortThird(arr);
        System.out.println(JSON.toJSONString(arr));
    }

    /**
     * 归并非递归实现
     *
     * @param arr 待排序数组
     */
    private static void mergeSortThird(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; i++) {
            //此处
            int left = 0;
            int right = left + i;
            int middle = (left + right) >> 1;
            while (right < n) {
                merge(arr, left, middle, right);
                left = right + 1;
                right = left + i;
                middle = (left + right) >> 1;
            }
            if (left < n && middle < n) {
                merge(arr, left, middle, n - 1);
            }
        }
    }

    /**
     * 归并排序非递归实现
     * https://www.cnblogs.com/itsharehome/p/11058010.html
     *
     * @param arr 待排序数组
     */
    private static void mergeSortSecond(int[] arr) {
        int n = arr.length;
        //归并排序最小排序范围是1，之后每次递增一倍
        for (int i = 1; i < n; i += i) {
            // 进行数组进行划分
            int left = 0;
            // 此处难以理解——数组是从零开始所以减一？
            int mid = left + i - 1;
            int right = mid + i;
            // 进行合并，对数组大小为 i 的数组进行两两合并
            while (right < n) {
                // 合并函数和递归式的合并函数一样
                merge(arr, left, mid, right);
                left = right + 1;
                mid = left + i - 1;
                right = mid + i;
            }
            // 还有一些被遗漏的数组没合并
            // 因为不可能每个字数组的大小都刚好为i
            if (left < n && mid < n) {
                merge(arr, left, mid, n - 1);
            }
        }
    }

    /**
     * 归并递归排序实现
     *
     * @param arr   待排序数据
     * @param start 排序数组开始索引
     * @param end   排序数组结束索引
     */
    private static void mergeSort(int[] arr, int start, int end) {
        if (arr == null || arr.length < 2) {
            return;
        }
        // 如果 left == right，表示数组只有一个元素，则不用递归排序
        if (start < end) {
            // 把大的数组分隔成两个数组
            int middle = (end + start) >> 1;
            // 对左半部分进行排序
            mergeSort(arr, start, middle);
            // 对右半部分进行排序
            mergeSort(arr, middle + 1, end);
            // 合并函数，把两个有序的数组合并起来
            merge(arr, start, middle, end);
        }
    }

    /**
     * 合并函数，把两个有序的数组合并起来
     * arr[left..middle]表示一个数组，arr[middle+1 .. right]表示一个数组
     *
     * @param arr    排序树组
     * @param left   数组开始索引
     * @param middle 数组中建索引
     * @param right  数组结束索引
     */
    private static void merge(int[] arr, int left, int middle, int right) {
        // 先用一个临时数组把他们合并汇总起来
        int[] a = new int[right - left + 1];
        int i = left;
        int j = middle + 1;
        int k = 0;

        while (i <= middle && j <= right) {
            if (arr[i] < arr[j]) {
                a[k++] = arr[i++];
            } else {
                a[k++] = arr[j++];
            }
        }
        while (i <= middle) {
            a[k++] = arr[i++];
        }
        while (j <= right) {
            a[k++] = arr[j++];
        }
        // 把临时数组复制到原数组
        for (i = 0; i < k; i++) {
            arr[left++] = a[i];
        }
    }

}
